﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using Xenta.Utils;

namespace Xenta.Web
{
    public abstract class WebPortal : ExtApplication
    {
        protected override IServiceContext ServiceContext(HttpContext context)
        {
            return new ServiceContext(context);
        }

        protected override bool IsContentRequest(HttpContext context)
        {
            return context.Request.Path.StartsWith("/Content",
                StringComparison.InvariantCultureIgnoreCase);
        }

        protected override void OnAcquireRequestState(object sender, EventArgs e)
        {
            base.OnAcquireRequestState(sender, e);
            if(IsContentRequest(Context))
                return;

            var data = Context.Request.RequestContext.RouteData;
            if(!data.Values.ContainsKey("locale"))
                return;

            string locale = ((string)data.Values["locale"]).Trim();
            if(String.IsNullOrWhiteSpace(locale))
                return;

            var svc = Context.Svc();
            var profile = svc.ProcessOperation("GetLocalizationProfileByCode", new
            {
                Code = locale,
                Fetch = "Entire"
            }, new
            {
                DisclosureLevel = "Entire"
            });
            if(profile == null || !JDM.HasFlags(profile.Flags, "Active", "Public"))
                return;

            svc.Localization = profile;
        }

        protected override void SetupViewEngine()
        {
            base.SetupViewEngine();
            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(new ViewEngine());
        }
    }

    public class ServiceContext : IServiceContext
    {
        #region Fields

        private readonly HttpContext _httpContext;
        private string _infrastructure;

        #endregion

        #region Ctors

        public ServiceContext(HttpContext context)
        {
            if(context == null)
                throw new ArgumentNullException("context");
            _httpContext = context;
        }

        #endregion

        #region Properties

        public HttpContext HttpContext
        {
            get
            {
                return _httpContext;
            }
        }

        public string Infrastructure
        {
            get
            {
                return _infrastructure;
            }
        }

        public IDictionary<String, String> Config
        {
            get
            {
                return null;
            }
        }

        #endregion

        #region Methods

        public Guid Initialize()
        {
            _infrastructure = ConfigurationManager.AppSettings[Par.In("App").Of(this).Name("Infrastructure")];
            if(String.IsNullOrWhiteSpace(_infrastructure))
            {
                var url = HttpContext.Request.Url;
                if(url.IsLoopback)
                    _infrastructure = "localhost";
                else if(url.HostNameType == UriHostNameType.Dns)
                {
                    string hostnamePattern = ConfigurationManager.AppSettings[Par.In("App").Of<Uri>().Name("HostnamePattern")];
                    if(!String.IsNullOrWhiteSpace(hostnamePattern))
                    {
                        var m = Regex.Match(url.Host, hostnamePattern);
                        if(m.Success && m.Groups["domain"].Success)
                            _infrastructure = m.Groups["domain"].Value;
                    }
                }
                if(String.IsNullOrWhiteSpace(_infrastructure))
                    _infrastructure = url.Host;
            }
            return Guid.NewGuid();
        }

        public Guid Authenticate()
        {
            if(HttpContext.User == null ||
                HttpContext.User.Identity == null ||
                !HttpContext.User.Identity.IsAuthenticated)
            {
                return Guid.Empty;
            }
            return HttpContext.User.Identity.Name.ParseAs(Guid.Empty);
        }

        #endregion
    }

    internal class ViewEngine : RazorViewEngine
    {
        #region Ctors

        public ViewEngine()
        {
            ViewLocationFormats = 
            MasterLocationFormats = 
            PartialViewLocationFormats = new[]
            {
                "~/Views/{1}/{3}/{4}/{0}.cshtml", // theme and locale, e.g. default/en-US/View.cshtml
                "~/Views/{1}/{3}/{0}.cshtml", // theme, e.g. default/View.cshtml
                "~/Views/{1}/{4}/{0}.cshtml", // locale, e.g. en-US/View.cshtml
                "~/Views/{1}/{0}.cshtml",
                "~/Views/Shared/{3}/{4}/{0}.cshtml", // theme and locale, e.g. default/en-US/View.cshtml
                "~/Views/Shared/{3}/{0}.cshtml", // theme, e.g. default/View.cshtml
                "~/Views/Shared/{4}/{0}.cshtml", // locale, e.g. en-US/View.cshtml
                "~/Views/Shared/{0}.cshtml"
            };
            AreaViewLocationFormats = 
            AreaMasterLocationFormats = 
            AreaPartialViewLocationFormats = new[]
            {
                "~/Areas/{2}/Views/{1}/{3}/{4}/{0}.cshtml", // theme and locale, e.g. default/en-US/View.cshtml
                "~/Areas/{2}/Views/{1}/{3}/{0}.cshtml", // theme, e.g. default/View.cshtml
                "~/Areas/{2}/Views/{1}/{4}/{0}.cshtml", // locale, e.g. en-US/View.cshtml
                "~/Areas/{2}/Views/{1}/{0}.cshtml",
                "~/Areas/{2}/Views/Shared/{3}/{4}/{0}.cshtml", // theme and locale, e.g. default/en-US/View.cshtml
                "~/Areas/{2}/Views/Shared/{3}/{0}.cshtml", // theme, e.g. default/View.cshtml
                "~/Areas/{2}/Views/Shared/{4}/{0}.cshtml", // locale, e.g. en-US/View.cshtml
                "~/Areas/{2}/Views/Shared/{0}.cshtml",
                "~/Views/Shared/{3}/{4}/{0}.cshtml", // theme and locale, e.g. default/en-US/View.cshtml
                "~/Views/Shared/{3}/{0}.cshtml", // theme, e.g. default/View.cshtml
                "~/Views/Shared/{4}/{0}.cshtml", // locale, e.g. en-US/View.cshtml
                "~/Views/Shared/{0}.cshtml"
            };
            FileExtensions = new[]
            {
                "cshtml"
            };
        }

        #endregion

        #region Methods

        public override ViewEngineResult FindView(ControllerContext ctx, 
            string view, 
            string master, 
            bool useCache)
        {
            if(ctx == null)
                throw new ArgumentNullException("ctx");
            if(String.IsNullOrWhiteSpace(view))
                throw new ArgumentNullException("view");
            var results = new List<String>();
            string area = (string)ctx.RouteData.DataTokens["area"];
            string theme = ctx.HttpContext.Svc().Config(Par.In("WebPortal").Name("Theme"), "Default");
            string locale = JDM.String(ctx.HttpContext.Svc().Localization.Code);
            string controller = ctx.RouteData.GetRequiredString("controller");
            string viewLocation = SearchView(ctx,
                String.IsNullOrWhiteSpace(area)
                    ? ViewLocationFormats
                    : AreaViewLocationFormats, 
                results,
                view,
                controller,
                area,
                theme,
                locale,
                "view", 
                useCache);
            bool isFound = !String.IsNullOrWhiteSpace(viewLocation);
            string masterLocation = String.Empty;
            if(isFound && !String.IsNullOrWhiteSpace(master))
            {
                masterLocation = SearchView(ctx,
                    String.IsNullOrWhiteSpace(area)
                        ? MasterLocationFormats
                        : AreaMasterLocationFormats,
                    results,
                    theme,
                    controller,
                    area,
                    master,
                    locale,
                    "master",
                    useCache);
                isFound = String.IsNullOrWhiteSpace(masterLocation);
            }
            return isFound 
                ? new ViewEngineResult(CreateView(ctx, viewLocation, masterLocation), this) 
                : new ViewEngineResult(results);
        }

        public override ViewEngineResult FindPartialView(ControllerContext ctx, 
            string view, 
            bool useCache)
        {
            if(ctx == null)
                throw new ArgumentNullException("ctx");
            if(String.IsNullOrWhiteSpace(view))
                throw new ArgumentNullException("view");
            var results = new List<String>();
            string area = (string)ctx.RouteData.DataTokens["area"];
            string theme = ctx.HttpContext.Svc().Config(Par.In("WebPortal").Name("Theme"), "Default");
            string locale = JDM.String(ctx.HttpContext.Svc().Localization.Code);
            string controller = ctx.RouteData.GetRequiredString("controller");
            string viewLocation = SearchView(ctx,
                String.IsNullOrWhiteSpace(area)
                    ? PartialViewLocationFormats
                    : AreaPartialViewLocationFormats, 
                results, 
                view, 
                controller, 
                area, 
                theme, 
                locale,
                "partial", 
                useCache);
            bool isFound = !String.IsNullOrWhiteSpace(viewLocation);
            return isFound
                ? new ViewEngineResult(CreatePartialView(ctx, viewLocation), this)
                : new ViewEngineResult(results);
        }

        #endregion

        #region Utilities

        private string SearchView(ControllerContext ctx,
            IEnumerable<String> locations,
            ICollection<String> results,
            string name, 
            string controller,
            string area,
            string theme,
            string locale,
            string cachePrefix, 
            bool useCache)
        {
            if(String.IsNullOrWhiteSpace(name))
                return String.Empty;
            if(locations == null || !locations.Any())
                return String.Empty;

            string cacheKey = String.Empty;
            if(useCache)
            {
                cacheKey = String.Format(CultureInfo.InvariantCulture,
                    ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:{5}:{6}",
                    GetType().AssemblyQualifiedName,
                    cachePrefix,
                    name,
                    controller,
                    area,
                    theme,
                    locale);
                string cached = ViewLocationCache.GetViewLocation(ctx.HttpContext, cacheKey);
                if(!String.IsNullOrEmpty(cached))
                    return cached;
            }

            string path = String.Empty;
            if(name.StartsWith("~") || name.StartsWith("/"))
            {
                results.Add(name);
                if(FileExists(ctx, name))
                    path = name;
            }
            else
            {
                foreach(var i in locations.Select(location => String.Format(CultureInfo.InvariantCulture,
                    location,
                    name,
                    controller,
                    area,
                    theme,
                    locale)))
                {
                    results.Add(i);
                    if(!FileExists(ctx, i)) 
                        continue;
                    path = i;
                    break;
                }
            }
            if(!String.IsNullOrWhiteSpace(path) && useCache)
                ViewLocationCache.InsertViewLocation(ctx.HttpContext, cacheKey, path);
            return path;
        }

        protected override bool FileExists(ControllerContext ctxt, string path)
        {
            try
            {
                return System.IO.File.Exists(ctxt.HttpContext.Server.MapPath(path));
            }
            catch(HttpException ex)
            {
                if(ex.GetHttpCode() != (int)HttpStatusCode.NotFound)
                    throw;
                return false;
            }
            catch
            {
                return false;
            }
        }

        #endregion
    } 

    public static class Extensions
    {
        public static string Theme(this UrlHelper helper, string path)
        {
            string theme = helper.RequestContext.HttpContext.Svc()
                .Config(Par.In("WebPortal").Name("Theme"), "Default");
            return helper.Content("/Content/{0}/{1}", theme, path);
        }

        public static string DateTimeRel(this Service svc, DateTime val)
        {
            var dtNow = DateTime.UtcNow;
            var diff = dtNow.Subtract(val);

            if(diff.TotalMinutes < 1)
                return String.Format(svc.Translate(Res.In("Common").Of("Date").Of("Rel").Name("Seconds")), diff.Seconds);
            else if(diff.TotalMinutes < 60)
                return String.Format(svc.Translate(Res.In("Common").Of("Date").Of("Rel").Name("Minutes")), diff.Minutes);
            else if(diff.TotalHours < 24)
                return String.Format(svc.Translate(Res.In("Common").Of("Date").Of("Rel").Name("Hours")), diff.Hours);
            else if(diff.TotalDays < 7)
                return String.Format(svc.Translate(Res.In("Common").Of("Date").Of("Rel").Name("Days")), diff.Days);
            return String.Format(svc.Translate(Res.In("Common").Of("Expr").Name("At")), svc.DateTime(val));
        }

        public static Loc Localized(this Loc location)
        {
            return location.Use("locale");
        }

        public static Loc Localized(this Loc location, string locale)
        {
            return location.Set("locale", locale);
        }
    }
}